﻿using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using AntDesign;
using Blazor.CoffeeTalesApi.Dto;
using Blazor.CoffeeTalesApi.Entity;
using Blazor.CoffeeTalesApi.Util;
using Blazor.Core;
using Blazor.Localization;
using Blazor.Pages.UpdateSetting.Modals.Whitelist;
using Blazor.Services.Device;
using Blazor.Services.Update;
using DynamicData;
using DynamicData.Binding;
using Microsoft.Extensions.Localization;
using ReactiveUI;
using HotUpdateInfo = Blazor.Model.Dto.HotUpdateInfo;

namespace Blazor.ViewModels.UpdateSetting;

public class SingleSettingViewModel: ViewModelBase
{
    private readonly IUpdateService _updateService;
    private readonly IStringLocalizer<UpdateSettingLocalization> _loc;
    private readonly IDeviceService _deviceService;
    private readonly ModalService _modalService;
    private ILogger _logger;
    
    private Channel Channel { get; }
    public uint NativeVersion { get; }
    
    private string _resourceUrl;

    public string ResourceUrl
    {
        get => _resourceUrl;
        private set => this.RaiseAndSetIfChanged(ref _resourceUrl, value);
    }

    private string _rawResourceVersion;

    public string RawResourceVersion
    {
        get => _rawResourceVersion;
        private set => this.RaiseAndSetIfChanged(ref _rawResourceVersion, value);
    }

    private string _defaultResourceVersion;

    public string DefaultResourceVersion
    {
        get => _defaultResourceVersion;
        private set => this.RaiseAndSetIfChanged(ref _defaultResourceVersion, value);
    }

    private string _resourceVersionComment;

    public string ResourceVersionComment
    {
        get => _resourceVersionComment;
        private set => this.RaiseAndSetIfChanged(ref _resourceVersionComment, value);
    }
    
    private readonly SourceCache<WhitelistEntryViewModel, IWhitelistKey> _whitelistCache = new(p => p.Key);

    public IObservableCollection<WhitelistEntryViewModel> Whitelist { get; } = new ObservableCollectionExtended<WhitelistEntryViewModel>();

    #region OAPH

    private readonly ObservableAsPropertyHelper<bool> _isChangingResourceUrl;
    public bool IsChangingResourceUrl => _isChangingResourceUrl.Value;

    private readonly ObservableAsPropertyHelper<bool> _isAddingWhitelist;
    public bool IsAddingWhitelist => _isAddingWhitelist.Value;

    private readonly ObservableAsPropertyHelper<bool> _isClearingWhitelist;
    public bool IsClearingWhitelist => _isClearingWhitelist.Value;

    #endregion

    #region Commands

    public ReactiveCommand<Unit, Unit> ChangeResourceUrl { get; }
    public ReactiveCommand<Unit, Unit> AddWhitelist { get; }
    public ReactiveCommand<Unit, Unit> ClearWhitelist { get; }

    #endregion

    #region Interactions

    public Interaction<string, Uri?> ChangeResourceUrlInteraction { get; } = new();
    public Interaction<Unit, WhitelistEntry?> NewWhitelistInteraction { get; } = new();
    public Interaction<Unit, bool> ClearWhitelistInteraction { get; } = new();

    public Interaction<WhitelistOptions, WhitelistResult?> ModifyResourceVersionEntryInteraction { get; } = new();
    public Interaction<Unit, bool> ConfirmDeleteInteraction { get; } = new();

    public Interaction<Unit, bool> ConfirmApplyInteraction { get; } = new();

    #endregion

    public SingleSettingViewModel(HotUpdateInfo data, IUpdateService updateService, IStringLocalizer<UpdateSettingLocalization> loc, IDeviceService deviceService, ILogger logger, Channel channel, uint nativeVersion, ModalService modalService)
    {
        _updateService = updateService;
        _loc = loc;
        _deviceService = deviceService;
        _logger = logger;
        Channel = channel;
        NativeVersion = nativeVersion;
        _modalService = modalService;

        _resourceUrl = data.ResourceUrl;
        _rawResourceVersion = data.ResourceVersion.Raw;
        _defaultResourceVersion = data.ResourceVersion.Default;
        _resourceVersionComment = data.ResourceVersion.Comment;
        
        _whitelistCache.Edit(updater =>
        {
            foreach (var entry in data.Whitelist)
            {
                var key = WhitelistKeyUtil.FromString(entry.Key);
                var vm = new WhitelistEntryViewModel(key, entry.Value, _updateService, _loc, deviceService, Channel, NativeVersion);
                _whitelistCache.AddOrUpdate(vm);
            }
        });
        
        ChangeResourceUrl = ReactiveCommand.CreateFromTask(ChangeResourceUrlImpl,
            this.WhenAnyValue(x => x.ResourceUrl).Select(i => true));
        ChangeResourceUrl.IsExecuting.ToProperty(this, x => x.IsChangingResourceUrl, out _isChangingResourceUrl);

        AddWhitelist = ReactiveCommand.CreateFromTask(AddWhitelistImpl, Observable.Return(true));
        AddWhitelist.IsExecuting.ToProperty(this, x => x.IsAddingWhitelist, out _isAddingWhitelist);

        ClearWhitelist = ReactiveCommand.CreateFromTask(ClearWhitelistImpl, Observable.Return(true));
        ClearWhitelist.IsExecuting.ToProperty(this,  x => x.IsClearingWhitelist, out _isClearingWhitelist);
        
        _whitelistCache.Connect()
            .Do(changes =>
            {
                foreach (var change in changes)
                {
                    if (change.Reason is not ChangeReason.Add)
                    {
                        continue;
                    }

                    ObserveWhitelistEntryViewModel(change.Current, Disposables);
                }
            })
            .Bind(Whitelist)
            .Subscribe();
    }

    private void ObserveWhitelistEntryViewModel(WhitelistEntryViewModel current, CompositeDisposable compositeDisposable)
    {
        current.Delete.Subscribe(success =>
        {
            if (success)
            {
                _whitelistCache.RemoveKey(current.Key);
            }
        }).DisposeWith(compositeDisposable);
        current.Copy.Subscribe(p =>
        {
            if (p is null)
            {
                return;
            }

            var (key, value) = p.Value;
            var vm = new WhitelistEntryViewModel(key, value, _updateService, _loc, _deviceService, Channel, NativeVersion);
            _whitelistCache.AddOrUpdate(vm);
        }).DisposeWith(compositeDisposable);
        current.Apply.Subscribe(entry =>
        {
            if (entry is null)
            {
                return;
            }

            RawResourceVersion = entry.Raw;
            DefaultResourceVersion = entry.Default;
            ResourceVersionComment = entry.Comment;
        }).DisposeWith(compositeDisposable);
    }

    #region Command Implements

    private async Task ChangeResourceUrlImpl()
    {
        var result = await ChangeResourceUrlInteraction.Handle(ResourceUrl);
        if (result is null)
        {
            return;
        }

        var success =
            await _updateService.SetResourceUrlAsync(Channel, NativeVersion, result.ToString());

        if (!success)
        {
            throw new Exception("Failed to set jump url");
        }

        ResourceUrl = result.ToString();
    }


    private async Task AddWhitelistImpl()
    {
        var result = await NewWhitelistInteraction.Handle(new Unit());

        if (result is null)
        {
            return;
        }

        if (_whitelistCache.Lookup(result.Key).HasValue)
        {
            throw new Exception("Whitelist already exists");
        }

        var success = await _updateService.AddWhitelistAsync(Channel, NativeVersion, result.Key, result.Value);
        if (!success)
        {
            throw new Exception("Failed to add whitelist");
        }

        var vm = new WhitelistEntryViewModel(result.Key, result.Value, _updateService, _loc, _deviceService, Channel, NativeVersion);
        _whitelistCache.AddOrUpdate(vm);
    }

    private async Task ClearWhitelistImpl()
    {
        var confirmed = await ClearWhitelistInteraction.Handle(new Unit());
        if (!confirmed)
        {
            return;
        }
        
        var success = await _updateService.ClearWhitelistAsync(Channel, NativeVersion);
        if (!success)
        {
            throw new Exception("Failed to clear whitelist");
        }
        
        _whitelistCache.Clear();
    }

    #endregion
}